package com.beanfactorypostprocessor;

import com.config.MapperConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;

import java.io.IOException;
import java.util.Arrays;

import static org.springframework.beans.factory.config.AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
import static org.springframework.core.io.ResourceLoader.CLASSPATH_URL_PREFIX;

/**
 * 扫描 @Mapper 标记的接口，并将其解析为 BeanDefinition 添加到 BeanFactory 中
 * BeanFactoryPostProcessor
 *
 * @author 罗俊华
 * @since 2023/10/26 21:52
 */
@Slf4j
public class MapperBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, Ordered {


    AnnotationBeanNameGenerator annotationBeanNameGenerator = new AnnotationBeanNameGenerator();

    PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();

    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();

    /**
     * 不建议直接往 BeanFactory 中注册单例对象，只应该往其中创建 BeanDefinition
     * 因为如果手动创建的单例又依赖于BeanFactory中的其他对象，那么会导致
     * postProcessBeanDefinitionRegistry() 先于 postProcessBeanFactory() 执行
     * 从 BeanFactory 中找不到目标依赖的对象，最终导致启动失败
     * <p>
     *   一旦使用BeanDefinition 将单例的依赖解耦之后，多个 BeanFactoryPostProcessor 之间就没有区分顺序的必要了
     *   例如 MapperBeanFactoryPostProcessor 和 FactoryBean_BeanFactoryPostProcessor 就无需再区分顺序
     * </p>
     * <p>
     * </p>
     * 因为创建 mapper 依赖于 SqlSessionFactoryBean 对象
     * ConfigurationClassPostProcessor 由 BeanDefinitionRegistryPostProcessor 对象 postProcessBeanDefinitionRegistry 方法先调用
     * 经过源码查看，所有的 BeanDefinitionRegistryPostProcessor 对象调用完成之后
     * 然后才调用 postProcessBeanFactory，所以为了简化优先级顺序，这里直接实现 BeanFactoryPostProcessor接口
     * 方便拿到 SqlSessionFactoryBean 对象
     *
     * @param beanFactory
     * @throws BeansException
     * @see ConfigurationClassPostProcessor
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }

    /**
     * 往BeanFactory中添加BeanDefinition来把创建对象的职责交给BeanFactory，手动实现mapper创建
     * @param registry
     * @throws BeansException
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        MapperScan mapperScan = AnnotationUtils.findAnnotation(MapperConfig.class, MapperScan.class);

        if (mapperScan == null) {
            return;
        }


        Arrays.stream(mapperScan.basePackages()).flatMap(basePackage -> {
            String classPath = CLASSPATH_URL_PREFIX + basePackage.replace(".", "/") + "/**/*.class";
            try {
//                注意这里是复数形式的 getResources() 方法
                return Arrays.stream(pathMatchingResourcePatternResolver.getResources(classPath));
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }).forEach(resource -> {

            try {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);

                AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();


//                hasMetaAnnotation 用于扫描组合注解，如果一个注解继承（派生）了 @Mapper ，那么它将返回true
                if (annotationMetadata.isInterface() && (annotationMetadata.hasAnnotation(Mapper.class.getName()) || annotationMetadata.hasMetaAnnotation(Mapper.class.getName()))) {


//                   mapper 是接口，直接添加到 BeanFactory 中会报错，需要动态生成代理
//                    registry.registerBeanDefinition(beanName,mapperBeanDefinition);
                    try {






/*    错误创建方式：1、不建议直接往 BeanFactory 中放入手动组装的单例
                  2、不建议这时从 BeanFactory 中手动获取依赖的对象
      目前学习到的都是通过调用 applicationContext.refresh() 之后才会创建单例对象
//                        这里还没有refresh() 所以没有拿到对象

                        Class<?> mapperClass = getClass().getClassLoader().loadClass(annotationMetadata.getClassName());

                        SqlSessionFactoryBean bean = beanFactory.getBean(SqlSessionFactoryBean.class);

                        MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<>(mapperClass);

                        mapperFactoryBean.setSqlSessionFactory(bean.getObject());

                        beanFactory.registerSingleton(mapperClass.getName(), mapperFactoryBean);

*/



/*                      正确方式，创建BeanDefinition
                        往BeanFactory中添加BeanDefinition来把创建对象的职责交给BeanFactory，手动实现mapper创建
                        虽然不建议用上面一种方式来创建单例，但是这里定义的 BeanDefinition 所做的事情和上面的方法步骤是一致的
 */
                        AbstractBeanDefinition mapperFactoryBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
//                                原本 MapperFactoryBean 的构造器需要一个 Mapper 接口的 class 对象
//                                但是这里给的是 class 对象的类名称
                                .addConstructorArgValue(annotationMetadata.getClassName())
//                                .addAutowiredProperty()
                                .setAutowireMode(AUTOWIRE_BY_TYPE)
                                .getBeanDefinition();

                        AbstractBeanDefinition mapperBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(annotationMetadata.getClassName()).getBeanDefinition();
                        String beanName = annotationBeanNameGenerator.generateBeanName(mapperBeanDefinition, registry);

                        log.info("注册mapper----> {} 其beanName为 {}", annotationMetadata.getClassName(), beanName);
                        registry.registerBeanDefinition(beanName, mapperFactoryBeanDefinition);

                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException(e);
                    }

                }


            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }


        });
    }
}
